#!/usr/bin/python

# This file is part of GUP.
#
# GUP is free software; you can redistribute it and/or modify it under the
# terms of the GNU General Public License as published by the Free Software
# Foundation; either version 2 of the License, or (at your option) any
# later version.
#
# GUP is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along
# with GUP; if not, write to the Free Software Foundation, Inc., 51
# Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
#
# Copyright (C) 2007 Julio Biason


'''Web Gallery 2.0 upload utility'''
__revision__ = '0.2.1'

import logging
import os.path
import os

from guplib.gallery import Gallery, GalleryError
from guplib.albums import AlbumList, AlbumDoesntExistError
from optparse import OptionParser
from ConfigParser import ConfigParser
from urllib2 import HTTPError

# licence info
LICENSE_TEXT = """
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA"""

# Exceptions
class NoSuchGalleryError(Exception): 
    '''The requested gallery doesn't exist.'''
    pass

class CantSelectAGalleryError(Exception): 
    '''The system can't come with a gallery because there is more than one
    saved.'''
    pass

def show_license():
    '''Print the system license.'''
    logging.info(LICENSE_TEXT)
    return

def parse_command_line():
    '''Command line option parsing.'''
    parser = OptionParser()
    parser.add_option('-g', '--gallery',
            help = 'Gallery configuration to be used',
            dest = 'gallery',
            action = 'store',
            default = '')
    parser.add_option('-r', '--reload',
            help = 'Reload the album list from the server',
            dest = 'reload',
            action = 'store_true',
            default = False)
    parser.add_option('-l', '--list-albums',
            help = 'List albums in the server',
            dest = 'list',
            action = 'store_true',
            default = False)
    parser.add_option('-s', '--search',
            help = 'Search for an album',
            dest = 'search',
            action = 'store',
            default = '')
    parser.add_option('--search-id',
            help = 'Show information about an album ID',
            dest = 'id',
            action = 'store',
            default = '')
    parser.add_option('-c', '--create',
            help = 'Create a new album; requires three parameters: ' + \
                    'parent album id (returned by the search options), ' + \
                    'album name (used in the filesystem in the server) ' + \
                    'and album title (used to display it in the albums)',
            dest = 'create',
            action = 'store',
            nargs = 3)
    parser.add_option('-a', '--album',
            help = 'Album to upload pictures',
            dest = 'album',
            action = 'store',
            default = '')
    parser.add_option('--delete',
            help = 'Delete the file locally if the upload succeeds',
            dest = 'delete',
            action = 'store_true',
            default = False)
    parser.add_option('-G', '--galery-url',
            help = 'Gallery URL',
            dest = 'url',
            default = '')
    parser.add_option('-u', '--user',
            help = 'User',
            dest = 'user',
            default = '')
    parser.add_option('-p', '--password',
            help = 'Password',
            dest = 'password',
            default = '')
    parser.add_option('--save',
            help = 'Save URL, user and password in a config ' + \
                    '(so future calls don''t need to specify them ' + \
                    'again); must specify a name for the gallery ' + \
                    'installation',
            dest = 'save',
            action = 'store',
            default = '')
    parser.add_option('--list-galleries',
            help = 'List saved galleries',
            dest = 'list_galleries',
            action = 'store_true',
            default = False)
    parser.add_option('-v', '--verbose',
            help = 'Verbose',
            dest = 'verbose',
            action = 'store_true',
            default = False)
    parser.add_option('--license',
            help = 'Print this program license',
            dest = 'license',
            action = 'store_true',
            default = False)
    (options, args) = parser.parse_args()

    return (options, args)

def locate_gallery(configuration, name):
    '''Look for a gallery in the configuration, or return the name of one
    if there is only one gallery.'''
    if name:
        if not configuration.has_section(name):
            raise NoSuchGalleryError, name
        return name

    sections = configuration.sections()
    if len(sections) == 0:
        return ''

    if len(sections) > 1:
        raise CantSelectAGalleryError

    return sections[0]

def merge_options(configuration, gallery, command_line_options):
    '''Merge the options from the gallery in the configuration file and the
    ones passed in the command line into a single location.'''
    if configuration.has_option(gallery, 'url'):
        config_url = configuration.get(gallery, 'url')

    if configuration.has_option(gallery, 'user'):
        config_user = configuration.get(gallery, 'user')

    if configuration.has_option(gallery, 'password'):
        config_password = configuration.get(gallery, 'password')

    if not command_line_options.url:
        command_line_options.url = config_url

    if not command_line_options.user:
        command_line_options.user = config_user

    if not command_line_options.password:
        command_line_options.password = config_password

    return command_line_options

def missing_options(options):
    '''Check if the required options are available.'''
    if not options.url:
        logging.error('Missing gallery URL')
        return True

    if not options.user:
        logging.error('Missing user')
        return True

    if not options.password:
        logging.error('Missing password')
        return True

    return False

def create_base_config_dir():
    '''Create the base configuration directory, where configuration and
    cache are stored.'''
    if not os.access(os.path.expanduser('~/.gup'), os.F_OK):
        os.mkdir(os.path.expanduser('~/.gup'))

def save_gallery_info(configuration, gallery_name, options):
    '''Save the gallery information back to the config file.'''
    logging.debug('Saving gallery "%s" information...', gallery_name)
    if not configuration.has_section(gallery_name):
        configuration.add_section(gallery_name)
    configuration.set(gallery_name, 'url'     , options.url)
    configuration.set(gallery_name, 'user'    , options.user)
    configuration.set(gallery_name, 'password', options.password)
    configuration.set(gallery_name, 'default', options.default)

    create_base_config_dir()
    contents = file(os.path.expanduser('~/.gup/config.ini'), 'w')
    configuration.write(contents)
    contents.close()

def gallery_cache_name(gallery_name):
    '''Return a string with the name of the gallery cache.'''
    return os.path.expanduser('~/.gup/%s.cache' % gallery_name)

def check_album_cache(gallery_name, options):
    '''Check if the album cache exists and, if it doesn't or the reload
    option was used, load it from the server. Returns a tuple with the
    album cache and a bool indicating if it needs to be saved.'''
    cache_name = gallery_cache_name(gallery_name)

    if not options.reload:
        try:
            os.stat(cache_name)
        except OSError:
            options.reload = True
            logging.info('There isn''t a cache for "%s", downloading...',
                    gallery_name)
    else:
        logging.info('Downloading album information for "%s"...',
                gallery_name)

    albums = AlbumList()
    if not options.reload:
        albums.load(cache_name)
        return (albums, False)

    # load from server
    connection = Gallery(options.url, options.user,
            options.password)
    for album in connection.fetch_albums_prune():
        albums.add(int(album[0]), album[1], int(album[2]))
    
    return (albums, True)

def display_albums(tree):
    '''Display the albums in a tree format.'''
    for (level, name, album_id) in tree.tree():
        logging.info('%s%s (%s)', '  ' * level, name, album_id)

def upload(options, files):
    '''Upload files to the gallery.'''
    connection = Gallery(options.url, options.user, options.password)
    total   = len(files)
    current = 0
    for image in files:
        current += 1
        logging.info('Uploading "%s" (%d/%d)...',
                image, current, total)
        try:
            connection.add_item(str(options.album), image)
            if options.delete:
                logging.info('Removing "%s"...',
                        image)
                os.remove(image)
        except HTTPError, e:
            logging.error('HTTP error: %s' % (e))

def save_album_cache(gallery_name, albums):
    '''Save the gallery cache.'''
    cache_name = gallery_cache_name(gallery_name)
    create_base_config_dir()
    albums.save(cache_name)

def make_name(album_list, stop_id):
    '''From a tree of albums, make a flat name for it.

    album_list - the tree of the album
    stop_id - id of the album (top stop generating the list, since the
        search_id function will return the children of the id.'''

    names = []
    tree = album_list.tree()
    tree.next() # skip the root node ('/')

    for (_, name, album_id) in tree:
        names.append(name)
        if album_id == stop_id:
            break

    return '/'.join(names)

def main():
    '''Main program'''
    (options, files) = parse_command_line()
    if options is None:
        return

    if options.verbose:
        logging_level = logging.DEBUG
    else:
        logging_level = logging.INFO

    logging.basicConfig(level = logging_level,
            format='%(message)s')

    if options.license:
        logging.info('GUP %s', __revision__)
        show_license()
        return

    # read the config
    config = ConfigParser()
    config.read([os.path.expanduser('~/.gup/config.ini')])

    # if list-galleries, list and exit
    if options.list_galleries:
        for section in config.sections():
            logging.info(section)
        return

    # locate a gallery; if there more than one, try options; if there are
    #    no options for default gallery, bail out
    try:
        gallery = locate_gallery(config, options.gallery)
    except NoSuchGalleryError, gal:
        logging.error('Gallery "%s" doesn''t exist', gal)
        return
    except CantSelectAGalleryError:
        logging.error('There is more than one gallery saved;')
        logging.error('Use the "-g" option to select one.')
        return

    # merge options
    if gallery:
        options = merge_options(config, gallery, options)

    # check missing options
    if missing_options(options):
        return

    # if save-options, save the config file
    if options.save:
        save_gallery_info(config, options.save, options)
        gallery = options.save # that's the current gallery now

    # if reload or cache doesn't exist, reload and keep going; mark cache
    #   info as updated
    try:
        (albums, save_cache) = check_album_cache(gallery, options)
    except GalleryError, msg:
        logging.error(str(msg))
        return

    # check the list option (list albums)
    if options.list:
        display_albums(albums)

    # check search option (search for an album with the name)
    if options.search:
        try:
            search = albums.search_name(options.search)
        except AlbumDoesntExistError:
            logging.error('There isn''t an album named "%s"',
                    options.search)
            return

        display_albums(search)

    # check the search id option (search for an album with the id)
    if options.id:
        try:
            search = albums.search_id(int(options.id))
        except AlbumDoesntExistError:
            logging.error('There isn''t an album with the ID "%s"',
                    options.id)
            return

        display_albums(search)

    # if create album, create album, add it to chache, and set it as
    #   current album
    if options.create:
        # check if the parent album exists
        try:
            albums.get_name(int(options.create[0]))
        except AlbumDoesntExistError:
            logging.error("Parent album %s doesn't exist" %
                    (options.create[0]))
            return

        logging.info('Creating album "%s"...', options.create[1])
        connection = Gallery(options.url, options.user,
                options.password)
        album_id = connection.new_album(options.create[0],
                options.create[1], options.create[2])
        if album_id is not None:
            logging.info('Created new album "%s", id "%s"' %
                    (options.create[1], album_id))
            options.album = album_id
        else:
            logging.error('Error creating album')
            return

        # add the album in the cache
        albums.add(int(album_id), options.create[2],
                int(options.create[0]))
        save_cache = True

    # if there is an album, add pictures to it
    if options.album and files:
        try:
            album_list = albums.search_id(int(options.album))
        except AlbumDoesntExistError:
            logging.error('There isn''t an album with the ID "%s"',
                    options.album)
            return

        logging.info('Uploading pictures to "%s"...' % 
                make_name(album_list, int(options.album)))

        upload(options, files)

    # if there was a change in the cache, save it
    if save_cache:
        save_album_cache(gallery, albums)

if __name__ == '__main__':
    main()
